import { Disposable } from "./../Disposable";

export class EventTarget extends Disposable {
  private listeners_: { [key: string]: Function[] } = {};
  private pendingRemovals_: { [key: string]: number } = {};
  private dispatching_: { [key: string]: number } = {};

  constructor() {
    super();
  }

  /**
   * @param {string} type Type.
   * @param {module:ol/events~ListenerFunction} listener Listener.
   */
  public addEventListener(type: string, listener: Function) {
    let listeners = this.listeners_[type];
    if (!listeners) {
      listeners = this.listeners_[type] = [];
    }
    if (listeners.indexOf(listener) === -1) {
      listeners.push(listener);
    }
  }

  /**
   * @param {string} type Type.
   * @param {module:ol/events~ListenerFunction} listener Listener.
   */
  public removeEventListener(type: string, listener: Function) {
    const listeners = this.listeners_[type];
    if (listeners) {
      const index = listeners.indexOf(listener);
      if (type in this.pendingRemovals_) {
        // make listener a no-op, and remove later in #dispatchEvent()
        listeners[index] = undefined;
        ++this.pendingRemovals_[type];
      } else {
        listeners.splice(index, 1);
        if (listeners.length === 0) {
          delete this.listeners_[type];
        }
      }
    }
  }

  /**
   * @param {{type: string, target: Function}} event Event or event type.
   * @return {boolean|undefined} `false` if anyone called preventDefault on the
   * event object or if any of the listeners returned false.
   */
  public dispatchEvent(type, data) {
    const listeners = this.listeners_[type];
    let propagate;
    if (listeners) {
      if (!(type in this.dispatching_)) {
        this.dispatching_[type] = 0;
        this.pendingRemovals_[type] = 0;
      }
      ++this.dispatching_[type];
      for (let i = 0, ii = listeners.length; i < ii; ++i) {
        if (listeners[i].call(this, data) === false) {
          propagate = false;
          break;
        }
      }
      --this.dispatching_[type];
      if (this.dispatching_[type] === 0) {
        let pendingRemovals = this.pendingRemovals_[type];
        delete this.pendingRemovals_[type];
        while (pendingRemovals--) {
          this.removeEventListener(type, undefined);
        }
        delete this.dispatching_[type];
      }
      return propagate;
    }
  }
}
